package com.cygsunri.aop;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Aspect
@Component
public class LogAdvice {

    private static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();


    // 定义一个切点：所有方法会织入advice
//    @Pointcut("execution(* com.cygsunri.wisdompark.callback.controller.*.*(..))  || execution(* com.cygsunri.aop.ExceptionControllerAdvice.*(..))")
    @Pointcut("execution(* com.cygsunri.wisdompark.callback.controller.*.*(..))")
    private void logAdvicePointcut() {}

    // Before表示logAdvice将在目标方法执行前执行
    @Before("logAdvicePointcut()")
    public void doBefore(JoinPoint joinPoint) {
        log.info("------------------------ Start  request----------------------------");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Signature signature = joinPoint.getSignature();
        // 记录下请求内容
        log.info("Request MethodType:{}", request.getMethod());
        log.info("Request IP:{}", request.getRemoteAddr());
        log.info("Request Url:{}", request.getRequestURL().toString());
        log.info("Request Class and Methods:{}.{}", signature.getDeclaringTypeName(), signature.getName());
        boolean result = checkParamType(signature);
        if (result) {
            log.info("文件格式不打印参数信息");
            return;
        }
        Object[] args = joinPoint.getArgs();
        //序列化时过滤掉request和response
        List<Object> logArgs = streamOf(args).filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                .collect(Collectors.toList());
        String argStr = JSON.toJSONString(logArgs);
        log.info("Request Args:{}", argStr);
    }

    /**
     * 判断参数类型是否是文件
     *
     * @param signature
     * @return
     */
    public boolean checkParamType(Signature signature) {
        MethodSignature methodSignature = (MethodSignature) signature;
        Class[] parameterTypes = methodSignature.getParameterTypes();
        for (Class cla : parameterTypes) {
            if (isFile(cla)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 过滤工具类
     *
     * @param array
     * @param <T>
     * @return
     */
    public static <T> Stream<T> streamOf(T[] array) {
        return ArrayUtils.isEmpty(array) ? Stream.empty() : Arrays.asList(array).stream();
    }

    /**
     * 调用方法以何种方式结束，都会执行
     */
    @After("logAdvicePointcut()")
    public void doAfter() {

    }

    /**
     * //在调用上面 @Pointcut标注的方法后执行。用于获取返回值
     *
     * @param obj
     */
    @AfterReturning(returning = "obj", pointcut = "logAdvicePointcut()")
    public void doAfterReturning(Object obj) {
        // 处理完请求，返回相应参数
        log.info("Response Result:{}", JSONObject.toJSONString(obj));
        log.info("------------------------ End request----------------------------");
    }

    /**
     * 文件上传字段不打印
     *
     * @param clazz
     * @return
     */
    private boolean isFile(Class clazz) {
        if (MultipartFile.class.isAssignableFrom(clazz)) {
            return true;
        }
        if ((clazz.isArray()) && (MultipartFile.class.isAssignableFrom(clazz.getComponentType()))) {
            return true;
        }
        return false;
    }
}